1 /**
2  * License:
3  *              Copyright Devisualization (Richard Andrew Cattermole) 2014 - 2017.
4  *     Distributed under the Boost Software License, Version 1.0.
5  *        (See accompanying file LICENSE_1_0.txt or copy at
6  *              http://www.boost.org/LICENSE_1_0.txt)
7  */
8 module devisualization.util.algorithms.bunnyhop;
9 import gl3n.linalg : vec3, dot;
10 
11 /**
12  * Based upon http://flafla2.github.io/2015/02/14/bunnyhop.html
13  */
14  struct BunnyHopSpeed {
15 	private {
16 		vec3 previousVelocity;
17 		float airAccel, airMaxAccel, groundAccel, groundMaxAccel;
18 		
19 		ubyte friction;
20 		float timeUnit;
21 	}
22  
23 	/**
24 	 * Base data for a bunny hop calculations
25 	 *
26 	 * Params:
27 	 * 		friction			=	The friction of the world. Must be between 1 and 5. Where 5 would cause gliding.
28 	 *		airAccel			=	Acceleration speed while in the air (e.g. jumps)
29 	 *		airMaxAccel			= 	The maximum acceleration in the air (e.g. jumps)
30 	 *		groundAccel			=	Acceleration speed while on the ground
31 	 *		groundMaxAccel		=	The maximum acceleration on the ground
32 	 * 		timeUnit			=	How long is a single time unit aka per frame? Default: 1
33 	 *		previousVelocity	=	What was the previous velocity that the character was at? Default: X: 0, Y: 0, Z: 0
34 	 */
35 	this(ubyte friction, float airAccel, float airMaxAccel, float groundAccel, float groundMaxAccel, float timeUnit = 1f, vec3 previousVelocity = vec3(0, 0, 0))
36 	in {
37 		assert(friction >= 1 && friction <= 5);
38 	} body {
39 		this.friction = friction;
40 		this.timeUnit = timeUnit;
41 		
42 		this.airAccel = airAccel;
43 		this.airMaxAccel = airMaxAccel;
44 		this.groundAccel = groundAccel;
45 		this.groundMaxAccel = groundMaxAccel;
46 		
47 		this.previousVelocity = previousVelocity;
48 	}
49 	
50 	/**
51 	 * Gets the current velocity and updates it to be the previous one
52 	 *
53 	 * Params:
54 	 *		accelDir	=	The direction the player has specified (e.g. arrow keys)
55 	 *		inAir		=	Is the player currently in the air? Default: false
56 	 *
57 	 * Returns:
58 	 * 		The current velocity
59 	 */
60 	vec3 newVelocity(vec3 accelDir, bool inAir = false) {
61 		import std.math : fmax;
62 		vec3 ret;
63 		
64 		if (inAir) {
65 			ret = accelerate(accelDir, airAccel, airMaxAccel);
66 		} else {
67 			float speed = cast(float)previousVelocity.magnitude;
68 			
69 			if (speed != 0) {
70 				float drop = speed * friction * timeUnit;
71 				previousVelocity *= fmax(speed - drop, 0f) / speed;
72 			}
73 			
74 			ret = accelerate(accelDir, groundAccel, groundMaxAccel);
75 		}
76 		
77 		previousVelocity = ret;
78 		return ret;
79 	}
80 	
81 	private vec3 accelerate(vec3 accelDir, float accelerate, float max_velocity) {
82 		float projVel = dot(previousVelocity, accelDir);
83 		float accelVel = accelerate * timeUnit;
84 		
85 		if (projVel + accelVel > max_velocity)
86 			accelVel = max_velocity - projVel;
87 		
88 		return previousVelocity + accelDir * accelVel;
89 	}
90  }